💡 AI 인사이트

🤖 AI가 여기에 결과를 출력합니다...

댓글 커뮤니티

쿠팡이벤트

이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

검색

    로딩 중이에요... 🐣

    [코담] 웹개발·실전 프로젝트·AI까지, 파이썬·장고의 모든것을 담아낸 강의와 개발 노트

    12 에러처리하기 | ✅ 저자: 이유정(박사)

    FastAPI에서 에러 처리(Error Handling)는 “클라이언트가 잘못된 요청을 보냈을 때”나 “서버 내부에서 예기치 못한 일이 발생했을 때” 적절한 HTTP 상태 코드와 메시지를 돌려줘서, API 소비자(프론트엔드나 다른 서비스)가 무슨 일이 생겼는지 알 수 있도록 돕는 기능입니다.

    HTTP 상태 코드(Status Code)란?

    • 200번대: 성공적인 요청
    • 400번대: 클라이언트 잘못(요청 자체 문제)
    • 500번대: 서버 내부 문제
      예:
      • 404 Not Found: 요청한 리소스가 없음
      • 422 Unprocessable Entity: 검증(Validation)에 실패
      • 500 Internal Server Error: 예기치 못한 서버 오류

    HTTPException으로 간단히 에러 내기 FastAPI가 제공하는 HTTPException 클래스를 raise하면, 지정한 상태 코드와 메시지(detail)가 그대로 클라이언트에 반환됩니다.


    Django에서는 에러 처리를 프레임워크가 자동으로 해주는 경우가 많습니다. Django는 웹 페이지 중심이라서 자동 처리와 기본 HTML에러 페이지를 기본으로 제공합니다. 그러나 FastAPI는 API 중심이라서 에러 처리를 직접 지정해야 합니다.


    FastAPI에서 에러를 처리하는 4가지 대표적인 방법: FastAPI에서는 에러를 처리할 때 상황에 따라 적절한 방식이 달라집니다. 아래 코드는 그에 따른 4가지 대표적인 처리 방식을 보여줍니다.


    1. 기본 HTTPException 사용하기
    # handling_exception.py
    
    from fastapi import FastAPI, HTTPException
    
    app = FastAPI()
    
    items = {    
    	"apple": "사과입니다",
        "banana": "바나나입니다",
        "grape": "포도입니다"
        }
    
    @app.get("/items/{item_id}") # items의 딕셔너리 키
    async def read_item(item_id: str):
        if item_id not in items:
            # 아이템이 없으면 404 에러를 발생시킵니다.
            raise HTTPException(status_code=404, detail="Item not found")
        return {"item": items[item_id]}
    
    • 가장 기본적이고 자주 쓰는 방식
    • 특정 조건에서 예외를 던지고 → FastAPI가 자동으로 JSON 에러 응답을 만들어 줌
    • 예: 404 Not Found, 403 Forbidden, 401 Unauthorized 등
      • 간단한 예외 처리에 적합
      • 별도 설정 없이 바로 사용 가능

    HTTPException + 커스텀 헤더

    # http_exception_with_custom_headers.py
    
    from fastapi import FastAPI, HTTPException
    
    app = FastAPI()
    
    items = {    
    	"apple": "사과입니다",
        "banana": "바나나입니다",
        "grape": "포도입니다"
        }
    
    @app.get("/items-header/{item_id}")
    async def read_item_header(item_id: str):
        if item_id not in items:
            # 헤더를 직접 지정해서 반환할 수 있습니다.
            raise HTTPException(
                status_code=404,
                detail="Item not found",
                headers={"X-Error": "There goes my error"},
            )
        return {"item": items[item_id]}
    
    • 에러 응답에 특정 헤더를 추가해서 전달하고 싶은 경우 사용
    • 예: 클라이언트에게 에러 코드나 상태를 헤더로 전달할 때 ✔ 보안, 상태, 오류 추적 등 목적의 메타정보 전달에 유용

    content-length: 26

    • 응답 본문의 바이트 크기입니다.
    • 즉, JSON 응답의 길이가 26바이트라는 뜻이에요. 예: 응답 내용이
    {"message": "hello world!"}
    

    같은 경우, 이 문자열의 길이를 byte 단위로 계산한 값입니다.

    content-type: application/json

    • 응답의 데이터 형식을 알려줍니다.
    • 여기서는 JSON 형식이라는 뜻이에요.
    • 브라우저나 앱은 이걸 보고 JSON으로 파싱하게 됩니다.

    date: Wed, 16 Jul 2025 15:16:39 GMT

    • 응답이 생성된 날짜와 시간(GMT 기준)입니다.
    • 클라이언트는 이걸 보고 응답 시점이나 캐시 처리에 참고할 수 있어요.

    server: uvicorn

    • 이 응답을 만들어낸 서버의 이름입니다.
    • uvicorn은 FastAPI가 사용하는 ASGI 웹 서버입니다. 즉, “이 응답은 uvicorn 서버가 만들어서 보냈습니다” 라는 뜻이에요.

    커스텀 예외 클래스 + 전용 핸들러

    # custom_exception_handler.py
    
    from fastapi import FastAPI, Request
    from fastapi.responses import JSONResponse
    
    # 1) 커스텀 예외 클래스 정의
    class UnicornException(Exception):
        def __init__(self, name: str):
            self.name = name
    
    app = FastAPI()
    
    # 2) 해당 예외 전용 핸들러 등록
    @app.exception_handler(UnicornException)
    async def unicorn_exception_handler(request: Request, exc: UnicornException):
        return JSONResponse(
            status_code=418,
            content={"message": f"Oops! {exc.name} did something. There goes a rainbow..."},
        )
    
    # 3) 라우트에서 예외 발생
    @app.get("/unicorns/{name}")
    async def read_unicorn(name: str):
        if name == "yolo":
            raise UnicornException(name=name)
        return {"unicorn_name": name}
    
    • 내가 정의한 특정 예외 클래스가 발생했을 때만,
      → 맞춤 응답 형식이나 메시지를 줄 수 있음

    • JSON 구조도 완전히 내 마음대로 조절 가능

      ✔ 특정 조건에서 전용 에러 메시지나 로직을 처리하고 싶을 때
      ✔ 사용자 친화적인 에러 메시지 필요할 때


    기본(Starlette) 핸들러 오버라이드

    # override_exception_handlers.py
    
    from fastapi import FastAPI, HTTPException, Request
    from fastapi.exceptions import RequestValidationError
    from fastapi.responses import PlainTextResponse
    from starlette.exceptions import HTTPException as StarletteHTTPException
    
    app = FastAPI()
    
    # 1) Starlette의 HTTPException 핸들러 오버라이드
    # # FastAPI 내부의 HTTPException도 결국 Starlette 기반이라 이 핸들러가 작동함
    @app.exception_handler(StarletteHTTPException)
    async def http_exception_handler(request: Request, exc: StarletteHTTPException):
        return PlainTextResponse(str(exc.detail), status_code=exc.status_code)
    # PlainTextResponse(...) → JSON 대신 텍스트 형태로 에러 메시지 응답
    
    
    
    
    # 2) 요청 검증 에러(RequestValidationError) 핸들러 오버라이드
    @app.exception_handler(RequestValidationError)
    async def validation_exception_handler(request: Request, exc: RequestValidationError):
        return PlainTextResponse(str(exc), status_code=400)
    
    # 3) 테스트용 엔드포인트
    @app.get("/items/{item_id}")
    async def read_item(item_id: int):
        # item_id가 3이면 418 에러 발생
        if item_id == 3:
            raise HTTPException(status_code=418, detail="Nope! I don't like 3.")
        return {"item_id": item_id}
    
    • FastAPI 내부에서 기본적으로 사용하는 에러 핸들러 자체를 덮어쓰기(override)

    • 모든 에러의 응답 형식을 전역적으로 통일하고 싶을 때 사용

      ✔ 전체 프로젝트에서 에러 메시지 구조, 응답 형태를 통일하고 싶을 때
      ✔ Swagger 문서용 에러 메시지를 재정의할 때


    1. from fastapi import FastAPI, HTTPException, Request

    🔹 HTTPException

    • HTTP 상태 코드 에러를 일으킬 때 사용하는 예외 클래스
    • 예: 404 Not Found, 401 Unauthorized, 403 Forbidden 등을 발생시킬 수 있어요

    raise HTTPException(status_code=404, detail="Not found")

    🔹 Request

    • 들어온 HTTP 요청의 전체 내용을 담고 있는 객체
    • 에러 핸들러 함수에서 보통 사용돼요
    @app.exception_handler(SomeException) 
    async def handler(request: Request, exc: SomeException):     
    ...
    

    2. from fastapi.exceptions import RequestValidationError

    • 요청(request)에 검증 오류(Validation Error)가 발생했을 때 자동으로 발생하는 예외 클래스
    • 예를 들어, 쿼리 파라미터 타입이 잘못되었거나, Pydantic 모델에 맞지 않는 데이터가 들어왔을 때
    @app.exception_handler(RequestValidationError) 
    async def validation_handler(request: Request, exc: RequestValidationError):     
    	return PlainTextResponse("입력값이 잘못됐습니다", status_code=400)
    

    3. from fastapi.responses import PlainTextResponse

    • 응답(Response)을 일반 텍스트로 반환하는 클래스
    • JSON이 아니라 그냥 문자열을 그대로 사용자에게 보여주고 싶을 때 사용

    return PlainTextResponse("오류 발생", status_code=400)


    4. from starlette.exceptions import HTTPException as StarletteHTTPException

    • FastAPI는 내부적으로 Starlette 프레임워크를 기반으로 작동합니다.
    • HTTPException은 FastAPI에도 있지만, Starlette에도 정의되어 있어요.
    • 이 코드는 Starlette에서 발생하는 HTTPException을 직접 처리하고 싶을 때 사용합니다.
    @app.exception_handler(StarletteHTTPException) 
    async def override_http_exception(request: Request, exc: StarletteHTTPException):     
    	return PlainTextResponse(str(exc.detail), status_code=exc.status_code)
    
    Starlette는 FastAPI의 핵심 엔진 역할을 해주는 하위 프레임워크예요.
    
    • FastAPI는 라우팅, 유효성 검사(Pydantic), 자동 문서화(OpenAPI) 등을 편리하게 제공합니다.
    • 하지만:
      • 요청(Request) 객체 처리
      • 응답(Response) 반환
      • 예외 처리(Exception)
      • 미들웨어(Middleware) : 사용자 요청이 들어오면 로그를 남기거나, 인증/보안 검사를 하거나, 응답을 변경하기도 하기도 하는 중간 계층입니다.
      • 웹소켓(WebSocket) : 기존 HTTP처럼 요청-응답 한 번으로 끝나는 게 아니라, 서버와 클라이언트가 실시간으로 양방향 통신을 할 수 있게 해주는 기술입니다. 예) 채팅앱, 실시간주식/게임 데이터전송, loT 디바이스 통신

    이런 낮은 수준의 HTTP 관련 기능은 모두 Starlette가 처리합니다.


    • @app.exception_handler(...) → 예외 발생 시 처리해줄 함수 등록
    • PlainTextResponse(...) → 응답을 "문자 그대로 텍스트"로 돌려줌
    TOP
    preload preload